# Copyright (c) 2005,2006,2007 Dynamic Solutions Inc. (support@dynamic-solutions.com)
#
# This file is part of DataZoomer.
#
# DataZoomer is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# DataZoomer is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""Stores session variables"""


import time, pickle

sessionLife = 60 # time in minutes

class DzSession:
    tablename = 'dz_sessions'
    def __init__(self,system):
        self.__system = system
        self.__kill = 0
        self.sid = None

    def __repr__(self):
        return ', '.join(['%s="%s"' % (k,v) for (k,v) in zip(self.__dict__,self.__dict__.values()) if k[0]!='_'])

    def __str__(self):
        return str(self.__dict__)

    def __clear(self):
        self.__kill = 0
        for key in self.__dict__.keys():
            if key[:12]<>'_DzSession__': del self.__dict__[key]

    def exec_SQL(self,cmd):
        curs = self.__system.db(cmd)
        return curs

    def create_session_database(self):
        cmd = """
            create table %s (
                sesskey varchar(32) NOT NULL default '',
                expiry int(11) NOT NULL default '0',
                status char(1) not null default 'D',
                value text NOT NULL,
                PRIMARY KEY (sesskey)
            ) type=MyISAM;
        """ % self.tablename
        self.exec_SQL(cmd)

    def gc(self):
        if self.__system.destroy_sessions:
            self.__system.debug('sessions garbage collected...')
            cmd = 'DELETE FROM %s WHERE (expiry < %s) or (status="D")' % (self.tablename,'%s')
            self.__system.db(cmd,time.time())

    def new_session(self,timeout=sessionLife):
        def trysid(sid):
            cmd = "INSERT INTO %s VALUES (%s,%s,'A','')" % (self.tablename,'%s','%s')
            try:
                self.__system.db(cmd,newsid,expiry)
                return 1
            except:
                return 0

        def make_session_id(st='nuthin'):
            import sys, random, time
            if sys.version_info[1]<5:
                import md5
                m = md5.md5()
            else:
                import hashlib
                m = hashlib.md5()
            m.update('This is DataZoomer')
            m.update(str(time.time()))
            m.update(str(random.random()))
            m.update(str(st))
            return m.hexdigest()

        self.gc()

        crazyloop = 10
        expiry = time.time() + timeout * 60
        newsid = make_session_id()
        success = trysid(newsid)

        # Try again in the unlikely event that the generated session id is being used
        while not success and crazyloop > 0:
            newsid = make_session_id()
            success = trysid(newsid)
            crazyloop -= 1

        if success:
            #self.__clear()
            self.sid = newsid
            return newsid
        else:
            raise 'Session error'

    def valid_sid(self,sid):
        if sid:
            if len(sid) != 32:
                raise 'Invalid session length: %d (%s)' % (len(sid),sid)
            if not sid.isalnum():
                raise 'Invalid session'

    def load_session(self,sid=None,anysession=0):
        if not sid or sid=='nosid':
            return None
        self.valid_sid(sid)

        if anysession:
            cmd = "SELECT * FROM %s WHERE sesskey=%s and status='A'" % (self.tablename,'%s')
            curs = self.__system.db(cmd,sid)
        else:
            cmd = "SELECT * FROM %s WHERE sesskey=%s AND expiry>%s and status='A'" %(self.tablename,'%s','%s')
            curs = self.__system.db(cmd,sid,time.time())

        if len(curs):
            try:
                values = (curs[0].VALUE and pickle.loads(curs[0].VALUE) or {})
            except:
                values = {}                
            for key in values:
                self.__dict__[key] = values[key]
            return 1
        return 0

    def save_session(self,sid=None,timeout=sessionLife):
        sid = sid or self.sid
        expiry = time.time() + timeout * 60
        values = {}
        for key in self.__dict__.keys():
            if key[0] != '_':
                values[key] = self.__dict__[key]
        value = pickle.dumps(values)
        cmd = 'UPDATE %s SET expiry=%s, value=%s WHERE sesskey=%s' % (self.tablename,'%s','%s','%s')
        curs = self.__system.db(cmd,expiry,value,sid)

    def kill(self):
        self.__kill = 1

    def iskill(self):
        return self.__kill

    def destroy_session(self,sid=None):
        sid = sid or self.sid
        ## remove session members
        cmd = "SELECT * FROM %s WHERE sesskey=%s AND expiry>%s and status='A'" %(self.tablename,'%s','%s')
        curs = self.__system.db(cmd,sid,time.time())
        if len(curs):
            values = (curs[0].VALUE and pickle.loads(curs[0].VALUE) or {})
            for key in values:
                del self.__dict__[key]

        self.__system.debug('Destroy: %s' % sid)
        if self.__system.destroy_sessions:
            cmd = 'delete from %s where sesskey=%s' % (self.tablename,'%s')
            self.__system.db(cmd,sid)
        else:
            cmd = 'update %s set expiry=%s, status="D" where sesskey=%s' % (self.tablename,'%s','%s')
            self.__system.db(cmd,time.time(),sid)

    def dump(self):
        cmd = "SELECT * FROM %s" % (self.tablename)
        curs = self.exec_SQL(cmd)
        import base64
        count = 0
        for rec in curs:
            print count,':',rec[0],time.asctime(time.localtime(rec[1])),'*',base64.decodestring(rec[3]+'\n'),'*'
            count += 1

    def __getattr__(self,name):
        if self.__dict__.has_key(name):
            return self.__dict__[name]
        else:
            return None


if __name__ == '__main__':
    import unittest
    import MySQLdb
    import dzsystem
    import datetime

    from zoom import zoomer

    debug = 1

    class SessionTest(unittest.TestCase):
        def test(self):
            #Create session object
            session = DzSession(zoomer)
            #Create new session
            id = session.new_session()
            self.assert_(id!='Session error')
            session.MyName = 'Test'
            session.Message = 'This is a test session'
            session.Number = 123
            #Save session
            session.save_session(id)
            self.assertEqual(len(session.exec_SQL('select * from '+session.tablename+' where sesskey="'+id+'"').__dict__['data']),1)
            # Create new session object
            session2 = DzSession(zoomer)
            # Load above session
            session2.load_session(id)
            self.assertEqual(session2.Number,123)
            self.assertEqual(session2.MyName,'Test')
            self.assertEqual(session2.Message,'This is a test session')
            session2.destroy_session(id)
            if not debug:
               self.assertEqual(len(session.exec_SQL('select * from '+session.tablename+' where sesskey="'+id+'"').__dict__['data']),0)
    unittest.main()
